package at.ac.tuwien.swa.sparrow.server.persistence

import at.ac.tuwien.swa.sparrow._
import at.ac.tuwien.swa.sparrow.common._
import org.springframework.stereotype.{Repository => ObjectRepository}
import scala.Some
import scala.collection.mutable

object Repository {

	def createSequence(start: Long = 1L, inc: Long = 1L): Stream[Long] = start #:: createSequence(start + inc, inc)
}

@ObjectRepository
class AccountRepository extends SimpleRepository[Account, Long] {

	override val objects = mutable.Map[Long, Account]() withDefault (id => throw new AccountDoesNotExistException(id))

	def unapply(entity: Account) = entity.id

	def findOne(username: String, password: String = null): Option[Account] =
		findOne(a => username == a._2.username) filter (a => password == null || (a passwordMatches password))

}

@ObjectRepository
class ClientRepository extends SimpleRepository[Client, Long] {

	def unapply(entity: Client) = entity.id
}

@ObjectRepository
class PeerRepository extends SimpleRepository[Peer, Long] {

	def unapply(entity: Peer) = entity.id
}

@ObjectRepository
class TrackRepository extends SimpleRepository[TrackMetadata, Long]() {

	def unapply(entity: TrackMetadata) = entity.id

	override def handleTransientId[S <: TrackMetadata](entity: S): S = findOne(entity) match {
		case Some(m: TrackMetadata) => m.asInstanceOf[S]
		case None =>
			entity.id = seq.next()
			entity
	}

	def findOne(info: TrackInfo): Option[TrackMetadata] = findOne(info.title, info.artist, info.album)

	def findOne(info: TrackMetadata): Option[TrackMetadata] = findOne(info.title, info.artist, info.album)

	def findOne(title: String, artist: String, album: String): Option[TrackMetadata] = {
		findOne(t => (title ?== t._2.title) && (artist ?== t._2.artist) && (album ?== t._2.album))
	}
}

@ObjectRepository
class SongRecognitionRepository extends SimpleRepository[SongRecognition, Long]() {

	def unapply(entity: SongRecognition) = entity.id
}

trait SimpleRepository[T, ID] {

	val objects = mutable.Map[ID, T]()

	val seq = (Repository createSequence()).iterator

	def apply(id: ID): T = objects(id)

	def apply(id: ID, default: => T): T = (objects get id) getOrElse default

	def unapply(entity: T): ID

	def deleteAll() = objects.clear()

	def delete(entity: T): mutable.Map[ID, T] = deleteOne(unapply(entity))

	def deleteOne(id: ID): mutable.Map[ID, T] = objects -= id

	def save[S <: T](entity: S): S = {
		var obj = entity
		val id = unapply(obj)
		if (id == 0L) {
			obj = handleTransientId(obj)
		}
		objects += (unapply(obj) -> obj)
		obj
	}

	def handleTransientId[S <: T](entity: S): S = {
		entity match {
			case po: PersistentObject => po.id = seq.next()
			case _ => throw TransientObjectException(entity)
		}
		entity
	}

	def findOne(id: ID): Option[T] = objects get id

	def findOne(p: ((ID, T)) => Boolean): Option[T] = objects find p match {
		case Some(e) => Some(e._2)
		case None => None
	}

	def exists(id: ID): Boolean = objects contains id

	def findAll(): Iterable[T] = objects.values.toList

	def findAll(p: ((ID, T)) => Boolean): Iterable[T] = (objects filter p).values.toList

	def count() = objects.size
}
